//
//  WCTextView.m
//  WabbitCode
//
//  Created by William Towe on 1/21/09.
//  Copyright 2009 Revolution Software. All rights reserved.
//


#import "WCTextView.h"
#import "WCMacros.h"
#import "WCRulerView.h"
#import "WCAppController.h"
#import "WCGotoLineSheetController.h"
#import "WCTextFile.h"
#import "WCLineHighlighter.h"
#import "WCSyntaxHighlighter.h"
#import "WCLayoutManager.h"
#import "WCInterfacePerformer.h"
#import "WCProject.h"
#import "WCProjectGroup.h"
#import <RegexKit/RegexKit.h>
#import "WCFindInProjectWindowController.h"
#import "NSString+WCExtensions.h"
#import "WCSymbol.h"
#import "WCBookmarksGroup.h"
#import "WCBookmark.h"
#import "WCBuildTargetsGroup.h"
#import "WCBuildTarget.h"
#import "WCBuildConfiguration.h"
#import "WCTextFileWindowController.h"


@interface WCTextView (Private)
- (void)_updateInfoStringWithNewRange:(NSRange)newRange;
@end

@implementation WCTextView
#pragma mark *** Subclass Overrides ***

- (void)dealloc {
#ifdef WCDEBUG
	NSLog(@"WCTextView dealloc");
#endif
	[self setDelegate:nil];
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorLineHighlightEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorLineHighlightColorKey"];
	
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorBackgroundColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorTabWidthKey"];
	
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorSyntaxHighlightingEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorDirectivesEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorDirectivesColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorConditionalRegistersEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorConditionalRegistersColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorOperationalCodesEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorOperationalCodesColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorOtherRegistersEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorOtherRegistersColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorLabelsEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorLabelsColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorPreProcessorCommandsEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorPreProcessorCommandsColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorStringsEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorStringsColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorBaseTenNumbersEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorBaseTenNumbersColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorBaseSixteenNumbersEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorBaseSixteenNumbersColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorBaseTwoNumbersEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorBaseTwoNumbersColorKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorCommentsEnabledKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsEditorCommentsColorKey"];
	
	[_infoString release];
	[super dealloc];
}

- (NSMenu *)menuForEvent:(NSEvent *)event {
	if ([super menuForEvent:event]) {
		static NSMenu *menu = nil;
		if (!menu) {
			menu = [[[WCInterfacePerformer sharedInstance] menuForProjectTextViewActionButton] copy];
			[menu setTitle:@"WCTextViewContextualMenu"];
			
			[menu removeItemAtIndex:0];
			
			NSUInteger index = 0;
			[menu insertItemWithTitle:NSLocalizedString(@"Cut",@"Cut") action:@selector(cut:) keyEquivalent:@"" atIndex:index++];
			[menu insertItemWithTitle:NSLocalizedString(@"Copy",@"Copy") action:@selector(copy:) keyEquivalent:@"" atIndex:index++];
			[menu insertItemWithTitle:NSLocalizedString(@"Paste",@"Paste") action:@selector(paste:) keyEquivalent:@"" atIndex:index++];
			[menu insertItem:[NSMenuItem separatorItem] atIndex:index];
		}
		return menu;
	}
	return nil;
}

- (NSArray *)completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
	NSMutableArray *comps = [NSMutableArray array];
	
	NSString *prefix = [[self string] substringWithRange:charRange];
	for (NSString *label in [[self project] projectSymbolsForAutocomplete]) {
		if ([label hasPrefix:prefix])
			[comps addObject:label];
	}
	return comps;
}

- (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange granularity:(NSSelectionGranularity)granularity {	
	if (granularity == NSSelectByWord) {
		NSRange lineRange = [[self string] lineRangeForRange:proposedSelRange];
		RKEnumerator *enumerator = [[[RKEnumerator alloc] initWithRegex:labelsRegex string:[[self string] substringWithRange:lineRange]] autorelease];
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			NSRange checkRange = NSMakeRange(lineRange.location+range->location, range->length);
			
			if (NSLocationInRange(proposedSelRange.location, checkRange)) {
				return checkRange;
			}
		}
	}
	return [super selectionRangeForProposedRange:proposedSelRange granularity:granularity];
}

- (void)insertText:(NSString *)value {
	[super insertText:value];
	
	if ([value isEqualToString:@"("] && [self automaticallyInsertClosingParenthesis]) {
		NSRange range = [self selectedRange];
		
		if ([self shouldChangeTextInRange:range replacementString:@")"]) {
			[self replaceCharactersInRange:range withString:@")"];
			[self didChangeText];
			[self setSelectedRange:range];
		}
	}
}

- (void)mouseDown:(NSEvent *)event {
	if (([event modifierFlags] & NSCommandKeyMask) && [event type] == NSLeftMouseDown && [event clickCount] == 2) {
		[super mouseDown:[NSEvent mouseEventWithType:NSLeftMouseDown location:[event locationInWindow] modifierFlags:0 timestamp:[event timestamp] windowNumber:[event windowNumber] context:[event context] eventNumber:[event eventNumber] clickCount:2 pressure:[event pressure]]];
		[self jumpToDefinitionAction:nil];
		return;
	}
	[super mouseDown:event];
}

- (void)keyDown:(NSEvent *)event {
	switch ([event keyCode]) {
		case kKeyCodeTab:
			if ([event modifierFlags] & NSControlKeyMask && [event modifierFlags] & NSShiftKeyMask) {
				[[[[self window] windowController] document] cycleToPreviousFirstResponder];
				return;
			}
			else if ([event modifierFlags] & NSControlKeyMask) {
				[[[[self window] windowController] document] cycleToNextFirstResponder];
				return;
			}
			break;
		default:
			break;
	}
	[super keyDown:event];
}

- (void)insertNewline:(id)sender; {
	[super insertNewline:sender];
	
	if (_automaticallyIndentNewLines) {
		NSString *previousLineWhitespaceString = nil;
		NSScanner *previousLineScanner = [[NSScanner alloc] initWithString:[[self string] substringWithRange:[[self string] lineRangeForRange:NSMakeRange([self selectedRange].location - 1, 0)]]];
		[previousLineScanner setCharactersToBeSkipped:nil];
		
		if ([previousLineScanner scanCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&previousLineWhitespaceString]) {
			[self insertText:previousLineWhitespaceString];
		}
		
		[previousLineScanner release];
	}
}

- (void)insertTab:(id)sender {
	NSRange range = [self selectedRange];
	if (!range.length) {
		[super insertTab:sender];
		return;
	}

	[self shiftRightAction:nil];
}
#pragma mark NSKeyValueObserving Protocol
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if ([(NSString *)context isEqualToString:kWCPrefsEditorLineHighlightEnabledKey]) {
		if (NSUserDefaultsBoolForKey(kWCPrefsEditorLineHighlightEnabledKey))
			[[WCLineHighlighter sharedInstance] highlightTextView:self];
		else
			[[WCLineHighlighter sharedInstance] unhighlightTextView:self];
	}
	else if ([(NSString *)context isEqualToString:kWCPrefsEditorLineHighlightColorKey]) {
		[[WCLineHighlighter sharedInstance] highlightTextView:self];
	}
	else if ([(NSString *)context isEqualToString:kWCPrefsEditorSyntaxHighlightingEnabledKey]) {
		if (NSUserDefaultsBoolForKey(kWCPrefsEditorSyntaxHighlightingEnabledKey))
			[[WCSyntaxHighlighter sharedInstance] highlightTextView:self];
		else
			[[WCSyntaxHighlighter sharedInstance] unhighlightTextView:self];
	}
	else if ([(NSString *)context isEqualToString:kWCPrefsEditorDirectivesEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorDirectivesColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorConditionalRegistersEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorConditionalRegistersColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorOperationalCodesEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorOperationalCodesColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorOtherRegistersEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorOtherRegistersColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorLabelsEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorLabelsColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorPreProcessorCommandsEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorPreProcessorCommandsColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorStringsEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorStringsColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorBaseTenNumbersEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorBaseTenNumbersColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorBaseSixteenNumbersEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorBaseSixteenNumbersColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorBaseTwoNumbersEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorBaseTwoNumbersColorKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorCommentsEnabledKey] ||
			 [(NSString *)context isEqualToString:kWCPrefsEditorCommentsColorKey]) {
		
		[[WCSyntaxHighlighter sharedInstance] highlightTextView:self];
	}
	else if ([(NSString *)context isEqualToString:kWCPrefsEditorBackgroundColorKey]) {
		[self setBackgroundColor:NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorBackgroundColorKey)];
	}
	else if ([(NSString *)context isEqualToString:kWCPrefsEditorTabWidthKey]) {
		[self setTabWidth];
	}
	else {
		[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
	}
}
#pragma mark NSUserInterfaceValidations Protocol
- (BOOL)validateMenuItem:(NSMenuItem *)item {
	SEL action = [item action];
	
	if (action == @selector(toUppercaseAction:)) {
		if (![self selectedRange].length)
			return NO;
	}
	else if (action == @selector(toLowercaseAction:)) {
		if (![self selectedRange].length)
			return NO;
	}
	else if (action == @selector(toggleAutomaticallyIndentNewLinesAction:)) {
		[item setState:_automaticallyIndentNewLines];
	}
	else if (action == @selector(toggleWrapLinesAction:)) {
		[item setState:_wrapLines];
	}
	else if (action == @selector(toggleShowGutterAction:)) {
		[item setState:_showGutter];
	}
	else if (action == @selector(toggleShowLineNumbersAction:)) {
		[item setState:[self showLineNumbers]];
		
		if (!_showGutter)
			return NO;
	}
	else if (action == @selector(toggleShowErrorsAction:)) {
		[item setState:[self showErrors]];
		
		if (!_showGutter)
			return NO;
	}
	else if (action == @selector(toggleShowWarningsAction:)) {
		[item setState:[self showWarnings]];
		
		if (!_showGutter)
			return NO;
	}
	else if (action == @selector(toggleShowInvisibleCharactersAction:)) {
		[item setState:[self showInvisibleCharacters]];
	}
	else if (action == @selector(jumpToDefinitionAction:)) {
		if (![self selectedRange].length)
			return NO;
		
		NSString *symbol = [[self string] substringWithRange:[self selectedRange]];
		if (![[[self project] projectLabels] containsObject:symbol] &&
			![[[self project] projectEquates] containsObject:symbol])
			return NO;
	}
	else if (action == @selector(findSelectedTextInProjectAction:)) {
		if (![self selectedRange].length)
			return NO;
	}
	else if (action == @selector(blockCommentUncommentAction:)) {
		if (![self selectedRange].length)
			return NO;
	}
	else if (action == @selector(toggleAutomaticallyInsertClosingParenthesisAction:)) {
		[item setState:_automaticallyInsertClosingParenthesis];
	}
	else if (action == @selector(addRemoveBreakpointAtCurrentLineAction:)) {
		if ([[self textFile] fileBreakpointAtLineNumber:[[self textFile] lineNumberForCharacterIndex:[self selectedRange].location]])
			[item setTitle:NSLocalizedString(@"Remove Breakpoint at Current Line",@"Remove Breakpoint at Current Line")];
		else
			[item setTitle:NSLocalizedString(@"Add Breakpoint at Current Line",@"Add Breakpoint at Current Line")];
	}
	else if (action == @selector(enableDisableBreakpointAtCurrentLineAction:)) {
		if ([[[self textFile] fileBreakpointAtLineNumber:[[self textFile] lineNumberForCharacterIndex:[self selectedRange].location]] isEnabled])
			[item setTitle:NSLocalizedString(@"Disable Breakpoint at Current Line",@"Disable Breakpoint at Current Line")];
		else
			[item setTitle:NSLocalizedString(@"Enable Breakpoint at Current Line",@"Enable Breakpoint at Current Line")];
	}
	else if (action == @selector(openInSeparateEditorAction:)) {
		if ([[[self window] windowController] isKindOfClass:[WCTextFileWindowController class]])
			return NO;
	}
	return YES;
}
#pragma mark NSTextView Delegate
- (NSUndoManager *)undoManagerForTextView:(NSTextView *)aTextView {
	return [[self textFile] fileUndoManager];
}

#pragma mark *** Public Methods ***
- (void)setupDefaults; {
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewFrameDidChangeNotification:) name:NSViewFrameDidChangeNotification object:[[self enclosingScrollView] contentView]];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewBoundsDidChangeNotification:) name:NSViewBoundsDidChangeNotification object:[[self enclosingScrollView] contentView]];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textStorageDidProcessEditingNotification:) name:NSTextStorageDidProcessEditingNotification object:[self textStorage]];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewDidChangeSelectionNotification:) name:NSTextViewDidChangeSelectionNotification object:self];
	
	// syntax highlighting enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorSyntaxHighlightingEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorSyntaxHighlightingEnabledKey];
	// assembler directives enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorDirectivesEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorDirectivesEnabledKey];
	// assembler directives color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorDirectivesColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorDirectivesColorKey];
	// conditional registers enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorConditionalRegistersEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorConditionalRegistersEnabledKey];
	// conditional registers color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorConditionalRegistersColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorConditionalRegistersColorKey];
	// operational codes enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorOperationalCodesEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorOperationalCodesEnabledKey];
	// operational codes color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorOperationalCodesColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorOperationalCodesColorKey];
	// other registers enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorOtherRegistersEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorOtherRegistersEnabledKey];
	// other registers color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorOtherRegistersColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorOtherRegistersColorKey];
	// labels enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorLabelsEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorLabelsEnabledKey];
	// labels color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorLabelsColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorLabelsColorKey];
	// pre processor commands enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorPreProcessorCommandsEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorPreProcessorCommandsEnabledKey];
	// pre processor commands color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorPreProcessorCommandsColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorPreProcessorCommandsColorKey];
	// strings enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorStringsEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorStringsEnabledKey];
	// strings color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorStringsColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorStringsColorKey];
	// base ten numbers enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorBaseTenNumbersEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorBaseTenNumbersEnabledKey];
	// base ten numbers color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorBaseTenNumbersColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorBaseTenNumbersColorKey];
	// base sixteen numbers enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorBaseSixteenNumbersEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorBaseSixteenNumbersEnabledKey];
	// base sixteen numbers color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorBaseSixteenNumbersColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorBaseSixteenNumbersColorKey];
	// base two numbers enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorBaseTwoNumbersEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorBaseTwoNumbersEnabledKey];
	// base two numbers color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorBaseTwoNumbersColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorBaseTwoNumbersColorKey];
	// comments enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorCommentsEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorCommentsEnabledKey];
	// comments color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorCommentsColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorCommentsColorKey];
	
	// editor font
	[self bind:@"font" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.kWCPrefsEditorFontKey" options:[NSDictionary dictionaryWithObjectsAndKeys:NSKeyedUnarchiveFromDataTransformerName, NSValueTransformerNameBindingOption, nil]];
	
	// editor text color
	[self bind:@"textColor" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.kWCPrefsEditorTextColorKey" options:[NSDictionary dictionaryWithObjectsAndKeys:NSKeyedUnarchiveFromDataTransformerName, NSValueTransformerNameBindingOption, nil]];
	
	// editor background color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorBackgroundColorKey" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:(void *)kWCPrefsEditorBackgroundColorKey];
	
	// tab width
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorTabWidthKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorTabWidthKey];
	
	// editor line highlight enabled
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorLineHighlightEnabledKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorLineHighlightEnabledKey];
	// editor line highlight color
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsEditorLineHighlightColorKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsEditorLineHighlightColorKey];
	
	[self setTabWidth];
	
	[self setFont:NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorFontKey)];
	
	[self setShowGutter:NSUserDefaultsBoolForKey(kWCPrefsEditorDefaultShowGutterKey)];
	[self setShowLineNumbers:NSUserDefaultsBoolForKey(kWCPrefsEditorDefaultShowLineNumbersKey)];
	[self setShowErrors:NSUserDefaultsBoolForKey(kWCPrefsEditorDefaultShowErrorsKey)];
	[self setShowWarnings:NSUserDefaultsBoolForKey(kWCPrefsEditorDefaultShowWarningsKey)];
	[self setWrapLines:NSUserDefaultsBoolForKey(kWCPrefsEditorDefaultWrapLinesKey)];
	[self setAutomaticallyIndentNewLines:NSUserDefaultsBoolForKey(kWCPrefsEditorDefaultAutomaticallyIndentNewLinesKey)];
	[self setAutomaticallyInsertClosingParenthesis:NSUserDefaultsBoolForKey(kWCPrefsEditorDefaultAutomaticallyInsertAClosingParenthesisKey)];
}
- (void)setTabWidth {
	// Set the width of every tab by first checking the size of the tab in spaces in the current font and then remove all tabs that sets automatically and then set the default tab stop distance
	NSMutableString *sizeString = [NSMutableString string];
	NSInteger numberOfSpaces = NSUserDefaultsIntegerForKey(kWCPrefsEditorTabWidthKey);
	while (numberOfSpaces--) {
		[sizeString appendString:@" "];
	}
	NSDictionary *sizeAttribute = [[[NSDictionary alloc] initWithObjectsAndKeys:NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorFontKey), NSFontAttributeName, nil] autorelease];
	CGFloat sizeOfTab = [sizeString sizeWithAttributes:sizeAttribute].width;
	
	NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
	
	NSArray *array = [style tabStops];
	for (id item in array) {
		[style removeTabStop:item];
	}
	[style setDefaultTabInterval:sizeOfTab];
	NSDictionary *attributes = [[[NSDictionary alloc] initWithObjectsAndKeys:style, NSParagraphStyleAttributeName, nil] autorelease];
	[self setTypingAttributes:attributes];
}
#pragma mark Creation
- (id)initWithFrame:(NSRect)frame textFile:(WCTextFile *)textFile inProject:(WCProject *)project; {
	if (!(self = [super initWithFrame:frame]))
		return nil;
	
	_textFile = textFile;
	_project = project;
	
	[self setDelegate:self];
	[self setRichText:NO];
	[self setAllowsUndo:YES];
	[self setUsesFontPanel:NO];
	[self setUsesFindPanel:YES];
	[self setUsesRuler:NO];
	
	return self;
}
#pragma mark Accessors
@synthesize infoString=_infoString,textFile=_textFile,wrapLines=_wrapLines, automaticallyIndentNewLines=_automaticallyIndentNewLines,showGutter=_showGutter,project=_project,automaticallyInsertClosingParenthesis=_automaticallyInsertClosingParenthesis,wordToHighlight=_wordToHighlight,shouldHighlightWord=_shouldHighlightWord;

@dynamic showInvisibleCharacters;
- (BOOL)showInvisibleCharacters {
	return [(WCLayoutManager *)[self layoutManager] showInvisibleCharacters];
}
- (void)setShowInvisibleCharacters:(BOOL)flag {
	[(WCLayoutManager *)[self layoutManager] setShowInvisibleCharacters:flag];
}

- (void)setWrapLines:(BOOL)flag; {	
	_wrapLines = flag;
	
	if (_wrapLines) {
		[[self enclosingScrollView] setHasHorizontalScroller:NO];
		[[self textContainer] setWidthTracksTextView:YES];
		[[self textContainer] setContainerSize:NSMakeSize([[self enclosingScrollView] contentSize].width, FLT_MAX)];
		[self setHorizontallyResizable:NO];
	}
	else {
		[[self textContainer] setContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
		[[self textContainer] setWidthTracksTextView:NO];
		[self setHorizontallyResizable:YES];
		[[self enclosingScrollView] setHasHorizontalScroller:YES];
	}
	[self setNeedsDisplay:YES];
}

- (void)setShowGutter:(BOOL)flag {
	_showGutter = flag;
	
	// create the ruler view if we don't have one
	if (![[self enclosingScrollView] verticalRulerView]) {
		WCRulerView *rv = [[[WCRulerView alloc] initWithScrollView:[self enclosingScrollView] textFile:_textFile inProject:_project] autorelease];
		[self setRulerVisible:YES];
		[[self enclosingScrollView] setVerticalRulerView:rv];
		[[self enclosingScrollView] setHasHorizontalRuler:NO];
	}
	
	if (_showGutter) {
		[self setRulerVisible:YES];
		[[self enclosingScrollView] setHasHorizontalRuler:NO];
	}
	else {
		[self setRulerVisible:NO];
	}
}

@dynamic showLineNumbers;
- (BOOL)showLineNumbers {
	return [[self rulerView] showLineNumbers];
}
- (void)setShowLineNumbers:(BOOL)flag {
	[[self rulerView] setShowLineNumbers:flag];
}

@dynamic showErrors;
- (BOOL)showErrors {
	return [[self rulerView] showErrors];
}
- (void)setShowErrors:(BOOL)flag {
	[[self rulerView] setShowErrors:flag];
}

@dynamic showWarnings;
- (BOOL)showWarnings {
	return [[self rulerView] showWarnings];
}
- (void)setShowWarnings:(BOOL)flag {
	[[self rulerView] setShowWarnings:flag];
}

@dynamic rulerView;
- (WCRulerView *)rulerView {
	return (WCRulerView *)[[self enclosingScrollView] verticalRulerView];
}
#pragma mark IBActions
- (IBAction)revealInGroupTreeAction:(id)sender; {
	[[self project] revealInGroupTreeAction:self];
}

- (IBAction)openInSeparateEditorAction:(id)sender; {
	[[self project] openInSeparateEditorAction:self];
}

- (IBAction)enableDisableBreakpointAtCurrentLineAction:(id)sender; {
	[[self textFile] toggleBreakpointAtLineNumber:[[self textFile] lineNumberForCharacterIndex:[self selectedRange].location]];
}

- (IBAction)addRemoveBreakpointAtCurrentLineAction:(id)sender {
	NSUInteger line = [[self textFile] lineNumberForCharacterIndex:[self selectedRange].location];
	if ([[self textFile] fileBreakpointAtLineNumber:line])
		[[self textFile] removeFileBreakpointAtLineNumber:line];
	else
		[[self textFile] addFileBreakpointAtLineNumber:line];
}

- (IBAction)addToBookmarksAction:(id)sender; {
	[[[self project] bookmarksGroup] addBookmarkAtLineNumber:[[self textFile] lineNumberForCharacterIndex:[self selectedRange].location] inTextFile:[self textFile]];
	[[self project] updateSwapView];
}

- (IBAction)jumpToDefinitionAction:(id)sender; {
	NSString *sString = [[self string] substringWithRange:[self selectedRange]];
	NSArray *symbols = [[self project] projectSymbolsArray];
	BOOL labelsAndMacrosAreCaseSensitive = (BOOL)[[[[[self project] buildTargetsGroup] activeBuildTarget] activeBuildConfiguration] labelsAndMacrosAreCaseSensitive];
	if (!labelsAndMacrosAreCaseSensitive)
		sString = [sString lowercaseString];
	
	for (WCSymbol *symbol in symbols) {
		NSString *name = (labelsAndMacrosAreCaseSensitive)?[symbol name]:[[symbol name] lowercaseString];
		if ([sString isEqualToString:name]) {
			WCTextFile *tf = [symbol textFile];
			WCTextView *tv = nil;
			
			if ([[[self window] windowController] isKindOfClass:[WCTextFileWindowController class]]) {
				WCTextFileWindowController *wc = [[self project] createSeparateEditorForTextFile:tf];
				tv = [wc textView];
			}
			else {
				NSTabViewItem *item = [[self project] addTabViewItemForTextFile:tf inTabViewContext:[[self project] currentTabViewContext]];
				[[self project] selectTabViewItemWithIdentifier:[item identifier] inTabViewContext:[[self project] currentTabViewContext]];
				tv = [[item view] documentView];
			}
			
			NSString *string = [tv string];
			NSRange range = [string lineRangeForRange:NSMakeRange([tf lineStartIndexForLineNumber:[symbol lineNumber]], 0)];
			
			range = [string rangeOfString:[symbol name] options:NSLiteralSearch range:range];
			
			if (range.location != NSNotFound) {
				[tv setSelectedRange:range];
				[tv scrollRangeToVisible:[tv selectedRange]];
				//[tv showFindIndicatorForRange:[tv selectedRange]];
				return;
			}
			break;
		}
	}
	NSBeep();
}

- (IBAction)gotoLineAction:(id)sender; {
	[WCGotoLineSheetController presentGotoLineSheetForTextView:self];
}

- (IBAction)shiftLeftAction:(id)sender; {
	NSRange range = [self selectedRange];
	NSRange lineRange = [[self string] lineRangeForRange:range];
	NSString *string = [[self string] substringWithRange:lineRange];
	NSMutableString *newString = [NSMutableString stringWithString:string];
	
	if (!range.length) {
		RKRegex *regex = [[[RKRegex alloc] initWithRegexString:@"^(\t)(.*)" options:RKCompileNoOptions] autorelease];
		
		[newString match:regex replace:RKReplaceAll withString:@"$2"];
		
		if ([self shouldChangeTextInRange:lineRange replacementString:newString]) {
			[self replaceCharactersInRange:lineRange withString:newString];
			[self didChangeText];
			[self setSelectedRange:NSMakeRange(range.location-1, 0)];
		}
	}
	else {
		RKRegex *regex = [[[RKRegex alloc] initWithRegexString:@"^(\t)(.*)" options:RKCompileMultiline] autorelease];
		
		[newString match:regex replace:RKReplaceAll withString:@"$2"];
		
		if ([self shouldChangeTextInRange:lineRange replacementString:newString]) {
			[self replaceCharactersInRange:lineRange withString:newString];
			[self didChangeText];
			[self setSelectedRange:NSMakeRange(lineRange.location, [newString length])];
		}
	}
}

- (IBAction)shiftRightAction:(id)sender; {
	NSRange range = [self selectedRange];
	NSRange lineRange = [[self string] lineRangeForRange:range];
	NSString *string = [[self string] substringWithRange:lineRange];
	
	if (!range.length) {
		NSMutableString *newString = [NSMutableString stringWithString:@"\t"];
		[newString appendString:string];
		
		if ([self shouldChangeTextInRange:lineRange replacementString:newString]) {
			[self replaceCharactersInRange:lineRange withString:newString];
			[self didChangeText];
			[self setSelectedRange:NSMakeRange(range.location+1, 0)];
		}
	}
	else {
		NSMutableString *newString = [NSMutableString stringWithString:string];
		RKRegex *regex = [[[RKRegex alloc] initWithRegexString:@"^(.*)" options:RKCompileMultiline] autorelease];
		
		[newString match:regex replace:RKReplaceAll withString:@"\t$1"];
		
		if ([self shouldChangeTextInRange:lineRange replacementString:newString]) {
			[self replaceCharactersInRange:lineRange withString:newString];
			[self didChangeText];
			[self setSelectedRange:NSMakeRange(lineRange.location, [newString length])];
		}
	}
}

- (IBAction)commentUncommentAction:(id)sender; {
	NSRange range = [self selectedRange];
	
	if (!range.length) {
		NSRange lineRange = [[self string] lineRangeForRange:range];
		NSString *string = [[self string] substringWithRange:lineRange];
		
		if ([string rangeOfString:@";" options:NSLiteralSearch].location == NSNotFound) {
			NSMutableString *newString = [NSMutableString stringWithString:@";;"];
			[newString appendString:string];
			
			if ([self shouldChangeTextInRange:lineRange replacementString:newString]) {
				NSRange range = [self selectedRange];
				[self replaceCharactersInRange:lineRange withString:newString];
				[self didChangeText];
				[self setSelectedRange:NSMakeRange(range.location+2, range.length)];
			}
		}
		else {
			NSMutableString *newString = [NSMutableString stringWithString:string];
			[newString replaceOccurrencesOfString:@";" withString:@"" options:NSLiteralSearch range:[newString wholeRange]];
			
			if ([self shouldChangeTextInRange:lineRange replacementString:newString]) {
				NSRange range = [self selectedRange];
				[self replaceCharactersInRange:lineRange withString:newString];
				[self didChangeText];
				[self setSelectedRange:NSMakeRange(range.location-([string length]-[newString length]), range.length)];
			}
		}		
	}
	else {
		NSRange lineRange = [[self string] lineRangeForRange:range];
		NSString *string = [[self string] substringWithRange:lineRange];
		
		RKRegex *mRegex = [[[RKRegex alloc] initWithRegexString:@"^\\s*;+.*" options:RKCompileMultiline] autorelease];
		if ([string rangeOfRegex:mRegex].location == NSNotFound) {
			RKRegex *regex = [[[RKRegex alloc] initWithRegexString:@"^(\\s*)(.*)" options:RKCompileMultiline] autorelease];
			NSMutableString *newString = [NSMutableString stringWithString:string];
			[newString match:regex replace:RKReplaceAll withString:@"$1;;$2"];
			
			if ([self shouldChangeTextInRange:lineRange replacementString:newString]) {
				[self replaceCharactersInRange:lineRange withString:newString];
				[self didChangeText];
				[self setSelectedRange:NSMakeRange(lineRange.location, [newString length])];
			}
		}
		else {
			RKRegex *regex = [[[RKRegex alloc] initWithRegexString:@"^(\\s*)(;+)(.*)" options:RKCompileMultiline] autorelease];
			NSMutableString *newString = [NSMutableString stringWithString:string];
			[newString match:regex replace:RKReplaceAll withString:@"$1$3"];
			
			if ([self shouldChangeTextInRange:lineRange replacementString:newString]) {
				[self replaceCharactersInRange:lineRange withString:newString];
				[self didChangeText];
				[self setSelectedRange:NSMakeRange(lineRange.location, [newString length])];
			}
		}
	}
}

- (IBAction)blockCommentUncommentAction:(id)sender; {
	NSRange range = [[self string] lineRangeForRange:[self selectedRange]];
	NSString *string = [[self string] substringWithRange:range];

	// the selection does not have a matching block comment
	if ([string rangeOfString:@"#comment" options:NSLiteralSearch].location == NSNotFound && [string rangeOfString:@"#endcomment" options:NSLiteralSearch].location == NSNotFound) {
		NSMutableString *newString = [NSMutableString stringWithString:@"#comment\n"];
		[newString appendString:string];
		[newString appendString:@"#endcomment\n"];
		
		if ([self shouldChangeTextInRange:range replacementString:newString]) {
			[self replaceCharactersInRange:range withString:newString];
			[self didChangeText];
			[self setSelectedRange:NSMakeRange(range.location, [newString length])];
		}
	}
	else {
		NSMutableString *newString = [NSMutableString stringWithString:string];
		[newString replaceOccurrencesOfString:@"#comment\n" withString:@"" options:NSLiteralSearch range:[newString wholeRange]];
		[newString replaceOccurrencesOfString:@"#endcomment\n" withString:@"" options:NSLiteralSearch range:[newString wholeRange]];
		[newString replaceOccurrencesOfString:@"#comment" withString:@"" options:NSLiteralSearch range:[newString wholeRange]];
		[newString replaceOccurrencesOfString:@"#endcomment" withString:@"" options:NSLiteralSearch range:[newString wholeRange]];
		
		if ([self shouldChangeTextInRange:range replacementString:newString]) {
			[self replaceCharactersInRange:range withString:newString];
			[self didChangeText];
			[self setSelectedRange:NSMakeRange(range.location, [newString length])];
		}
	}
}

- (IBAction)findSelectedTextInProjectAction:(id)sender {
	NSString *string = [[self string] substringWithRange:[self selectedRange]];
	
	[[self project] findInProjectAction:nil];
	[[[self project] findInProjectWindowController] setFindString:string];
	[[[self project] findInProjectWindowController] setScopeIdentifier:kWCFindInProjectAllFilesIdentifier];
	[[[self project] findInProjectWindowController] findAction:nil];
}

- (IBAction)toUppercaseAction:(id)sender; {
	NSString *string = [[[self string] substringWithRange:[self selectedRange]] uppercaseString];
	[self insertText:string];
}

- (IBAction)toLowercaseAction:(id)sender; {
	NSString *string = [[[self string] substringWithRange:[self selectedRange]] lowercaseString];
	[self insertText:string];
}

- (IBAction)toggleWrapLinesAction:(id)sender; {
	[self setWrapLines:!_wrapLines];
}

- (IBAction)toggleAutomaticallyIndentNewLinesAction:(id)sender; {
	[self setAutomaticallyIndentNewLines:!_automaticallyIndentNewLines];
}

- (IBAction)toggleAutomaticallyInsertClosingParenthesisAction:(id)sender; {
	[self setAutomaticallyInsertClosingParenthesis:!_automaticallyInsertClosingParenthesis];
}

- (IBAction)toggleShowGutterAction:(id)sender; {
	[self setShowGutter:!_showGutter];
}

- (IBAction)toggleShowLineNumbersAction:(id)sender; {
	[self setShowLineNumbers:![self showLineNumbers]];
}

- (IBAction)toggleShowErrorsAction:(id)sender; {
	[self setShowErrors:![self showErrors]];
}

- (IBAction)toggleShowWarningsAction:(id)sender; {
	[self setShowWarnings:![self showWarnings]];
}

- (IBAction)toggleShowInvisibleCharactersAction:(id)sender; {
	[self setShowInvisibleCharacters:![self showInvisibleCharacters]];
}
#pragma mark *** Private Methods ***
- (void)_updateInfoStringWithNewRange:(NSRange)newRange; {
	NSRange range = newRange;
	unsigned line = [[self rulerView] lineNumberForCharacterIndex:range.location];
	unsigned column = range.location - [[self rulerView] lineStartForLineNumber:line];
	
	[self setInfoString:[NSString stringWithFormat:@"%u:%u", ++line, ++column]];
}

#pragma mark Notifications
- (void)textViewDidChangeSelectionNotification:(NSNotification *)note {
	[self _updateInfoStringWithNewRange:[self selectedRange]];
	[[self textFile] setFileSelectedRange:[self selectedRange]];
	[[WCLineHighlighter sharedInstance] highlightTextView:self];
}

- (void)viewFrameDidChangeNotification:(NSNotification *)note {
	[[WCSyntaxHighlighter sharedInstance] highlightTextView:self];
}

- (void)viewBoundsDidChangeNotification:(NSNotification *)note {
	[[WCSyntaxHighlighter sharedInstance] highlightTextView:self];
}

- (void)textStorageDidProcessEditingNotification:(NSNotification *)note {
	// we have to recolor the visible portion for multiline comments to work correctly
	[[WCSyntaxHighlighter sharedInstance] performSelector:@selector(highlightTextView:) withObject:self afterDelay:0.0];
}
@end
